package com.griddroid.nodes;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import java.util.Vector;
import android.os.Handler;
import android.os.Message;
import com.griddroid.computations.Computation;
import com.griddroid.computations.Integral;
import com.griddroid.computations.Product;
import com.griddroid.computations.Sum;
import com.griddroid.configuration.GridClientConfig;
import com.griddroid.configuration.GridConfig;
import com.griddroid.exceptions.NotCalculatedComputationException;
import com.griddroid.messages.EndSessionAckMessage;
import com.griddroid.messages.PingMessage;
import com.griddroid.works.WorkRequest;
import com.griddroid.works.WorkResponse;

/**
 * This class represents a Grid Client Node. It extends the abstract class
 * {@link GridNode GridNode} to gain the main functionality of.
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 */
public class GridClient extends GridNode
{
	private Vector<WorkRequest> worksToSend;

	/**
	 * Creates a GridClient with the appropriate Handler to communicate with the
	 * activity. It also starts the the communication with the server.
	 * 
	 * @param msghandler
	 *            Handler to communicate with the activity.
	 */
	public GridClient(Handler msghandler)
	{
		this.messageHandler = msghandler;
		this.type = NodeType.CLIENT;
		this.log("Info", "Client started");
		worksToSend = new Vector<WorkRequest>();

		try
		{
			this.connectToServer();
		}
		catch (UnknownHostException e)
		{
			this.log("Connection error", "Could not connect to server. Unknown host!");
			System.exit(0);
		}
		catch (IOException e)
		{
			this.log("Connection error", "Could not connect to server. IOException!" + e.getMessage());
			System.exit(0);
		}
	}

	/**
	 * Starts the client. Actually is a loop checking for timeouts, sending {@link WorkRequest WorkRequest}, 
	 * receiving all available messages ({@link PinkMessage}, {@link WorkResponse WorkResponse}, {@link EndSessionAckMessage EndSessionAckMessage})
	 * 
	 */
	public void start()
	{
		while (true)
		{
			if (this.forceExit)
				return;
			// Check if server is alive
			this.checkCounter++;
			this.checkForTimeout();

			// check for new work request to send
			while (!this.worksToSend.isEmpty())
			{
				WorkRequest wr = this.worksToSend.remove(this.worksToSend.size() - 1);
				try
				{
					this.send(this.handler, wr);
					this.log("Info", "Just sent work request");
					Message m_start = Message.obtain();
					m_start.what = GridClientConfig.START_WAITING;
					messageHandler.sendMessage(m_start);
				}
				catch (IOException e)
				{
					this.log("Error", "Could not send work request to the client." + e.getLocalizedMessage());
				}
			}

			// Check for new messages and handle them
			Object temp = null;
			try
			{
				while (this.handler.getInputStream().available() > 0)
				{
					temp = this.receive(this.handler);

					if (temp instanceof PingMessage)
					{
						this.getPingMessage(temp);
					}
					else if (temp instanceof WorkResponse)
					{
						this.log("Info", "Got work response message");
						try
						{
							this.getWorkResponse((WorkResponse) temp);
							Message m_stop = Message.obtain();
							m_stop.what = GridClientConfig.STOP_WAITING;
							messageHandler.sendMessage(m_stop);
						}
						catch (NotCalculatedComputationException e)
						{
							this.log("Error", "Could not print result");
						}
					}
					else if (temp instanceof EndSessionAckMessage)
					{
						this.getEndAckSessionMessage(temp);
					}
					else
					{
						this.log("Error", "Received unknown message!");
					}
				}
			}
			catch (IOException e1)
			{
				this.log("Error", "Could not check for new messages");
			}
			catch (ClassNotFoundException e1)
			{
				this.log("Error", "Could not check for new messages");
			}

			try
			{
				Thread.sleep(GridClientConfig.CHECKINTERVAL);
			}
			catch (InterruptedException e)
			{
			}
		}
	}

	/**
	 * Closes the client and sends a message to the activity, though the handler, to close.
	 */
	public void close()
	{
		this.forceExit = true;
		this.log("Error", "Client exitting...");
		Message m = Message.obtain();
		m.what = GridConfig.CLOSE;
		this.messageHandler.sendMessage(m);
	}

	/**
	 * Receives a {@link WorkResponse WorkResponse} and sends the result to the activity through the handler.
	 * @param wr
	 * 			WorkResponse which was just received.
	 * @throws NotCalculatedComputationException
	 * 			thrown when the work response is not calculated
	 */
	private void getWorkResponse(WorkResponse wr) throws NotCalculatedComputationException
	{

		this.log("Info", "" + wr.getComputation().getResult());
		Message m_result = Message.obtain();
		m_result.what = GridClientConfig.WORK_RESPONSE;
		m_result.obj = (Double) wr.getComputation().getResult();
		messageHandler.sendMessage(m_result);
	}

	/**
	 * Receives a String with an expression and converts it to a WorkRequest
	 * @param txt
	 * 		Text with the expression
	 */
	public void workRequestFromString(String txt)
	{
		txt = txt.replace('{', '~');
		txt = txt.replace('}', '~');
		StringTokenizer strtok = new StringTokenizer(txt, "~");
		String token = strtok.nextToken();
		Computation c;
		if (token.equals("\\sum_"))
		{
			// read bottom limit
			token = strtok.nextToken();
			int base;
			try
			{
				base = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with base number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with base number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read ^ character
			token = strtok.nextToken();
			if (token.length() > 1 || !token.equals("^"))
			{
				this.log("Error", "Problem with delimiter.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with delimiter.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read upper limit
			token = strtok.nextToken();
			int end;
			try
			{
				end = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with upper limit number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with upper limit number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read function
			token = strtok.nextToken();
			String function = token.trim();

			if (strtok.hasMoreTokens())
			{
				this.log("Error", "Malformed expression. Found more tokens than expected!");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Malformed expression. Found more tokens than expected!";
				messageHandler.sendMessage(m_error);
				return;

			}
			c = new Sum(base, end, function);

			WorkRequest wq = new WorkRequest(c, this.getId());
			this.worksToSend.add(wq);

		}
		else if (token.equals("\\prod_"))
		{
			// read bottom limit
			token = strtok.nextToken();
			int base;
			try
			{
				base = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with base number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with base number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read ^ character
			token = strtok.nextToken();
			if (token.length() > 1 || !token.equals("^"))
			{
				this.log("Error", "Problem with delimiter.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with delimiter.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read upper limit
			token = strtok.nextToken();
			int end;
			try
			{
				end = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with upper limit number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with upper limit number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read function
			token = strtok.nextToken();
			String function = token.trim();

			if (strtok.hasMoreTokens())
			{
				this.log("Error", "Malformed expression. Found more tokens than expected!");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Malformed expression. Found more tokens than expected!";
				messageHandler.sendMessage(m_error);
				return;

			}
			c = new Product(base, end, function);

			WorkRequest wq = new WorkRequest(c, this.getId());
			this.worksToSend.add(wq);
		}
		else if (token.equals("\\int_"))
		{
			// read bottom limit
			token = strtok.nextToken();
			int base;
			try
			{
				base = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with base number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with base number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read ^ character
			token = strtok.nextToken();
			if (token.length() > 1 || !token.equals("^"))
			{
				this.log("Error", "Problem with delimiter.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with delimiter.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read upper limit
			token = strtok.nextToken();
			int end;
			try
			{
				end = Integer.parseInt(token);
			}
			catch (NumberFormatException e)
			{
				this.log("Error", "Problem with upper limit number.");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Problem with upper limit number.";
				messageHandler.sendMessage(m_error);
				return;
			}

			// read function
			token = strtok.nextToken();
			String function = token.trim();

			if (strtok.hasMoreTokens())
			{
				this.log("Error", "Malformed expression. Found more tokens than expected!");
				Message m_error = Message.obtain();
				m_error.what = GridClientConfig.ERROR;
				m_error.obj = "Malformed expression. Found more tokens than expected!";
				messageHandler.sendMessage(m_error);
				return;

			}
			c = new Integral(base, end, 10000, function);

			WorkRequest wq = new WorkRequest(c, this.getId());
			this.worksToSend.add(wq);
		}
		else
		{
			Message m_error = Message.obtain();
			m_error.what = GridClientConfig.ERROR;
			m_error.obj = "Malformed expression not identified!" + token;
			messageHandler.sendMessage(m_error);
			return;
		}
	}

	/**
	 * Constructs a GridClient and starts it.
	 * @param args
	 */
	public static void main(String args[])
	{
		GridClient client = new GridClient(null);
		client.start();
	}
}
